﻿/**
 *@description:
 *@file: HelloSphere.cpp
 *@author: snow-tyan
 *@email: zziywang@163.com
 *@date: 22 March 2023
 *@copyright: 2018-2022 AiRiA. All rights reserved.
 */

#include "mygl/util.h"
#include "mygl/shader.h"
#include "mygl/resource_manager.h"

#include <iostream>
#include <fstream>
#include <memory>

#include <opencv2/opencv.hpp>
#include <glad/glad.h>   // 管理OpenGL的函数指针
#include <GLFW/glfw3.h>  // 提供窗口和OpenGL context

#ifndef PROJECT_ROOT
#define PROJECT_ROOT "F:/VisualStudioProjects/my-open-gl"
#endif

#define X_SEGMENTS 50
#define Y_SEGMENTS 50

using namespace mygl;

// settings
const std::string kProjectRoot = PROJECT_ROOT + std::string("/");
const std::string kWorkSpaceRoot = kProjectRoot + "01-GettingStarted/01-HelloTriangle/";
const std::string kShaderRoot = kWorkSpaceRoot + "";
const unsigned int kWindowWidth = 800;
const unsigned int kWindowHeight = 600;
const char* kWindowName = "HelloSphere";

void FramebufferSizeCallback(GLFWwindow* window, int width, int height);
void ProcessInput(GLFWwindow* window);

int main() {
  glfwInit();
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 核心模式

  GLFWwindow* window =
      glfwCreateWindow(kWindowWidth, kWindowHeight, kWindowName, NULL, NULL);
  if (window == NULL) {
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
  }
  glfwMakeContextCurrent(window);
  glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);

  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
  }
  // configure opengl global variable
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  // Load Shader, Compile, Link
  std::string vertex_path = kShaderRoot + "triangle_shader.vert";
  std::string fragment_path = kShaderRoot + "triangle_shader.frag";
  auto shader = std::make_unique<mygl::Shader>(vertex_path, fragment_path);
  if (shader->Init()) {
    fprintf(stderr, "Shader Init Failed!\n");
    exit(-1);
  }

  std::vector<GLfloat> sphere_vertices;
  std::vector<GLshort> sphere_indices;
  // spawn sphere vertex
  for (int y = 0; y <= Y_SEGMENTS; ++y) {
    for (int x = 0; x <= X_SEGMENTS; ++x) {
      GLfloat x_seg = static_cast<GLfloat>(x) / static_cast<GLfloat>(X_SEGMENTS);
      GLfloat y_seg = static_cast<GLfloat>(y) / static_cast<GLfloat>(Y_SEGMENTS);
      GLfloat x_pos = std::cos(x_seg * 2 * kMathPi) * std::sin(y_seg * kMathPi);
      GLfloat y_pos = std::cos(y_seg * kMathPi);
      GLfloat z_pos = std::sin(x_seg * 2 * kMathPi) * std::sin(y_seg * kMathPi);
      sphere_vertices.push_back(x_pos);
      sphere_vertices.push_back(y_pos);
      sphere_vertices.push_back(z_pos);
    }
  }
  for (int i = 0; i < Y_SEGMENTS; ++i) {
    for (int j = 0; j < X_SEGMENTS; ++j) {
      sphere_indices.push_back(i * (X_SEGMENTS + 1) + j);
      sphere_indices.push_back((i + 1) * (X_SEGMENTS + 1) + j);
      sphere_indices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1);
      sphere_indices.push_back(i * (X_SEGMENTS + 1) + j);
      sphere_indices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1);
      sphere_indices.push_back(i * (X_SEGMENTS + 1) + j + 1);
    }
  }

  GLuint vao, vbo;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * sphere_vertices.size(),
               sphere_vertices.data(), GL_STATIC_DRAW);
  GLuint ebo;
  glGenBuffers(1, &ebo);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLshort) * sphere_indices.size(),
               sphere_indices.data(), GL_STATIC_DRAW);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), nullptr);
  glEnableVertexAttribArray(0);
  // Unbind
  glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
  glBindVertexArray(GL_NONE);

  glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
#ifdef SAVE_VIDEO
  // 写视频需要H264编码，依赖OpenH264 Library
  // https://github.com/cisco/openh264/releases
  cv::VideoWriter video_writer;
  // int codec = cv::VideoWriter::fourcc('X', '2', '6', '4');  // OpenH264
  int codec = cv::VideoWriter::fourcc('a', 'v', 'c', '1');
  // int codec = cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); // OpenCV self-contained avi
  printf("codec = %d\n", codec);
  if (!video_writer.open("triangle.mp4", codec, 25.0,
                         cv::Size(kWindowWidth, kWindowHeight), true)) {
    fprintf(stderr, "VideoWriter open %s failed\n", "save.mp4");
    exit(-1);
  }
#endif
  // render loop
  // -----------
  while (!glfwWindowShouldClose(window)) {
    // input
    // -----
    ProcessInput(window);

    // render
    // ------
    glClear(GL_COLOR_BUFFER_BIT);

    shader->UseProgram();
    glBindVertexArray(vao);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glDrawElements(GL_TRIANGLES, X_SEGMENTS * Y_SEGMENTS * 6, GL_UNSIGNED_SHORT, nullptr);

#ifdef SAVE_VIDEO
    cv::Mat out_mat{kWindowHeight, kWindowWidth, CV_8UC3};
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0, 0, kWindowWidth, kWindowHeight, GL_RGB, GL_UNSIGNED_BYTE,
                 out_mat.data);
    cv::flip(out_mat, out_mat, 0);
    cv::cvtColor(out_mat, out_mat, cv::COLOR_BGR2RGB);
    // cv::imwrite("triangle.png", out_mat);
    video_writer << out_mat;
#endif
    glfwSwapBuffers(window);
    glfwPollEvents();
  }
  shader->DeleteProgram();
  glDeleteVertexArrays(1, &vao);
  glDeleteBuffers(1, &vbo);
  glDeleteBuffers(1, &ebo);
#ifdef SAVE_VIDEO
  video_writer.release();
#endif
  glfwTerminate();
  return 0;
}

void ProcessInput(GLFWwindow* window) {
  if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
    glfwSetWindowShouldClose(window, true);
  }
}

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
  glViewport(0, 0, width, height);
}
